package jmine.tec.report.impl.table;

import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Queue;

import jmine.tec.report.api.table.ReportCell;
import jmine.tec.report.api.table.ReportColumn;
import jmine.tec.report.api.table.ReportRow;
import jmine.tec.report.api.table.ReportTableGroup;

/**
 * Iterador de linhas de tabela de relatórios. Contrói as linhas de forma lazy.
 * 
 * @param <V> tipo de valor dos registros
 * @author lundberg
 */
public class TableRowIterator<V> implements Iterator<ReportRow<V>> {

    private final Iterator<V> records;

    private final List<ReportColumn<V>> columns;

    private final List<ReportTableGroup<V>> groups;

    private final Queue<ReportRow> buffer;

    private boolean first = true;

    /**
     * Construtor
     * 
     * @param records records
     * @param columns columns
     * @param groups groups
     */
    public TableRowIterator(Iterator<V> records, List<ReportColumn<V>> columns, List<ReportTableGroup<V>> groups) {
        super();
        this.records = records;
        this.columns = Collections.unmodifiableList(columns);
        this.groups = groups;
        this.buffer = new LinkedList<ReportRow>();
    }

    /**
     * {@inheritDoc}
     */
    public boolean hasNext() {
        if (this.buffer.isEmpty()) {
            this.doProcess();
        }
        return !this.buffer.isEmpty();
    }

    /**
     * {@inheritDoc}
     */
    public ReportRow next() {
        if (this.buffer.isEmpty()) {
            this.doProcess();
        }
        return this.buffer.remove();
    }

    /**
     * {@inheritDoc}
     */
    public void remove() {
        throw new UnsupportedOperationException();
    }

    /**
     * Processa o próximo registro, caso exista, e adiciona as novas linhas ao buffer.
     */
    private void doProcess() {
        if (!this.records.hasNext()) {
            return;
        }
        V currentRecord = this.records.next();
        List<ReportTableGroup<V>> activeGroups = new LinkedList<ReportTableGroup<V>>();
        for (ReportTableGroup<V> group : this.reverse(this.groups)) {
            if (this.first) {
                activeGroups.add(group);
            } else if (!this.first && group.isGroupChange(currentRecord)) {
                activeGroups.add(group);
                this.buffer.addAll(group.onGroupEnd());
            }
            group.processRecord(currentRecord);
        }
        this.first = false;
        for (ReportTableGroup<V> group : this.reverse(activeGroups)) {
            this.buffer.addAll(group.onGroupStart());
        }
        this.buffer.add(this.createContentRow(currentRecord));
        if (!this.records.hasNext()) {
            for (ReportTableGroup<V> group : this.reverse(this.groups)) {
                this.buffer.addAll(group.onGroupEnd());
            }
        }
    }

    /**
     * @param <T> tipo genérico da lista
     * @param list list
     * @return reversed list
     */
    private <T> List<T> reverse(List<T> list) {
        List<T> reverse = new ArrayList<T>(list);
        Collections.reverse(reverse);
        return reverse;
    }

    /**
     * Cria a linha de relatório contendo o conteúdo propriamente dito do registro.
     * 
     * @param currentRecord currentRecord
     * @return ReportRow
     */
    private ReportRow createContentRow(V currentRecord) {
        Map<String, ReportCell> cellMap = new HashMap<String, ReportCell>();
        for (ReportColumn<V> column : this.columns) {
            cellMap.put(column.getId(), column.createCell(currentRecord));
        }
        return new ReportRowImpl<V>(currentRecord, cellMap);
    }
}
